package service

import (
	"context"
	"database/sql"
	"fx_swift_boss/api"
	"fx_swift_boss/internal/model"
	"fx_swift_boss/internal/temple"
	"fx_swift_boss/pkg/code"
	"fx_swift_boss/pkg/crypt"
	"fx_swift_boss/pkg/global"
	"fx_swift_boss/pkg/internalerrors"
	"fx_swift_boss/pkg/jwt"
	"fx_swift_boss/pkg/logger"
	"fx_swift_boss/pkg/mail"
	"fx_swift_boss/pkg/utils"
	"github.com/gogf/gf/v2/container/gvar"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
)

type ILoginSrv interface {
	Register(ctx context.Context, req *api.RegisterReq) error

	Login(ctx context.Context, req *api.LoginReq) (*api.LoginRes, error)

	SendMailCode(ctx context.Context, mail string) error

	ForgetPassword(ctx context.Context, req *api.ForgetPasswordReq) error

	UpdatePassword(ctx context.Context, req *api.UpdatePasswordReq) error

	SignOut(ctx context.Context) error
}

type loginSrv struct {
}

func NewLoginSrv() ILoginSrv {
	return &loginSrv{}
}

// 注册
func (l *loginSrv) Register(ctx context.Context, req *api.RegisterReq) error {
	var (
		err            error
		hashedPassword string
		salt           string
		result         sql.Result
		num            int
		rowsNums       int64
		redisResult    *gvar.Var
		redisCodes     string
		account        string
	)
	// 比对验证码
	redisResult, err = g.Redis().Get(ctx, "boss_"+req.Email)
	if err != nil {
		logger.SugarLogger.Error(err)
		return internalerrors.New(code.ErrUnknown, "验证码不正确")
	}
	if redisResult.Val() != nil {
		redisCodes = redisResult.String()
		if redisCodes != req.Code {
			return internalerrors.New(code.ErrUnknown, "验证码不正确")
		}
	} else {
		return internalerrors.New(code.ErrUnknown, "验证码不正确")
	}

	// 查询邮箱是否被注册
	if num, err = global.GFDB.Ctx(ctx).Schema("card").Model("fx_agent").Where("email", req.Email).Count(); err != nil || num == 1 {
		return internalerrors.New(code.ErrUnknown, "邮箱已经被注册了")
	}

	if hashedPassword, salt, err = crypt.HashPassword(req.Password); err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}

	account = utils.GetHashCode(utils.GenerateCode32())
	db := g.DB().Schema("card")
	return db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		if result, err = tx.Insert("fx_agent", &model.FxBoss{
			Account:  account,
			Email:    req.Email,
			Password: hashedPassword,
			Salt:     salt,
		}); err != nil {
			logger.SugarLogger.Error(err)
			return internalerrors.New(code.ErrUnknown, "注册失败")
		}
		rowsNums, err = result.RowsAffected()
		if rowsNums == 0 {
			return internalerrors.New(code.ErrUnknown, "注册失败")
		}
		return nil
	})
}

// 登陆
func (l *loginSrv) Login(ctx context.Context, req *api.LoginReq) (*api.LoginRes, error) {
	var (
		err                    error
		token                  string
		bossObj                *model.FxBoss
		getAuthToUserOrRoleRes *api.GetAuthToUserOrRoleRes
	)
	// 根据帐号查询
	if err = global.GFDB.Ctx(ctx).Model("fx_boss").Where("email=?", req.Email).Scan(&bossObj); err != nil || bossObj == nil {
		logger.SugarLogger.Error(err)
		return nil, internalerrors.New(code.ErrUnknown, "该帐号存在异常")
	}

	// compare password
	if err = bossObj.ComparePassword(req.Password); err != nil {
		return nil, internalerrors.New(code.ErrPasswordIncorrect, "密码不匹配")
	}

	// generate token
	if token, err = jwt.Gen(bossObj.Account, "boss"); err != nil {
		return nil, internalerrors.New(code.ErrUnknown, "token generate failed")
	}

	// set token to redis
	if err = g.Redis().SetEX(ctx, bossObj.Account, token, 172800); err != nil {
		return nil, internalerrors.New(code.ErrUnknown, "token keep failed")
	}

	// 获取相关权限
	getAuthToUserOrRoleRes, err = NewAuthSrv().GetAuthToUserOrRole(ctx, &api.GetAuthToUserOrRoleReq{
		Account: bossObj.Account,
	})

	return &api.LoginRes{
		Token:    token,
		Account:  bossObj.Account,
		Email:    bossObj.Email,
		AuthInfo: getAuthToUserOrRoleRes.AuthInfo,
	}, nil
}

// 退出登陆
func (l *loginSrv) SignOut(ctx context.Context) error {
	var (
		err          error
		resultNums   int64
		accountToken string
	)
	accountToken = ctx.Value("Account").(string)
	resultNums, err = g.Redis().Del(ctx, accountToken)
	if err != nil || resultNums == 0 {
		return internalerrors.New(code.ErrBind, "帐号异常")
	}
	return err
}

// 发送code到邮箱
func (l *loginSrv) SendMailCode(ctx context.Context, email string) error {
	var (
		err error
	)
	defer func() {
		if r := recover(); r != nil {
			logger.SugarLogger.Error(err)
		}
	}()
	// generate random validation code
	codes := utils.GenerateVerificationCode(6)

	if !VerifyEmailFormat(email) {
		return internalerrors.New(code.ErrUnknown, "邮箱格式不正确")
	}

	// 存入redis中并且设置过期时间
	if err = g.Redis().SetEX(ctx, "boss_"+email, codes, 300); err != nil {
		return internalerrors.New(code.ErrUnknown, "验证码发送失败")
	}
	go mail.SendMail(email, codes, temple.SendMailHtmlStr, global.ViperObj.GetString("email.send_code"))

	return err
}

// 忘记密码
func (l *loginSrv) ForgetPassword(ctx context.Context, req *api.ForgetPasswordReq) error {
	var (
		bossObj              *model.FxBoss
		err                  error
		redisResult          *gvar.Var
		redisCodes           string
		result               sql.Result
		rowsNums             int64
		hashedPassword, salt string
	)

	// 根据帐号查询
	if err = global.GFDB.Ctx(ctx).Model("fx_boss").Where("email=?", req.Email).
		Scan(&bossObj); err != nil {
		return internalerrors.New(code.ErrUnknown, "帐号查询异常")
	}
	if bossObj == nil {
		return internalerrors.New(code.ErrUnknown, "请先去注册")
	}
	// 比对验证码
	redisResult, err = g.Redis().Get(ctx, bossObj.Email)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "验证码不正确")
	}
	if redisResult.Val() != nil {
		redisCodes = redisResult.String()
		if redisCodes != req.Code {
			return internalerrors.New(code.ErrUnknown, "验证码不正确")
		}
	} else {
		return internalerrors.New(code.ErrUnknown, "验证码不正确")
	}
	// update password
	hashedPassword, salt, err = crypt.HashPassword(req.NewPassword)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, err.Error())
	}
	result, err = global.GFDB.Model("fx_boss").Data(g.Map{"password": hashedPassword, "salt": salt}).
		Where("account=?", bossObj.Account).Update()
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	rowsNums, err = result.RowsAffected()
	if rowsNums == 0 {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	return nil
}

// 更新密码
func (l *loginSrv) UpdatePassword(ctx context.Context, req *api.UpdatePasswordReq) error {
	var (
		bossObj  *model.FxBoss
		err      error
		result   sql.Result
		rowsNums int64
		account  string
	)
	account = ctx.Value("Account").(string)
	// 根据帐号查询
	if err = global.GFDB.Ctx(ctx).Model("fx_boss").Where("account=?", account).
		Scan(&bossObj); err != nil || bossObj == nil {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	if err = bossObj.ComparePassword(req.OldPassword); err != nil {
		return internalerrors.New(code.ErrPasswordIncorrect, "password not match")
	}
	hashedPassword, salt, err := crypt.HashPassword(req.NewPassword)
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	result, err = global.GFDB.Model("fx_boss").Data(g.Map{"password": hashedPassword, "salt": salt}).
		Where("account=?", account).Update()
	if err != nil {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	rowsNums, err = result.RowsAffected()
	if rowsNums == 0 {
		return internalerrors.New(code.ErrUnknown, "更新密码失败")
	}
	return err
}
